/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package se.kth.lwr.jeat.core;

import javax.measure.quantity.Quantity;
import javax.measure.unit.Unit;
import org.jscience.physics.amount.Amount;

/**This class represents a function of Amount sources. It offers basic
 * mathematical operations with these sources.
 * 
 * Be aware that this class is not mutable. Function methods (as multiply)
 * acts directly on the object reference and returns an updated version of it.
 *
 * @author davidev
 */
public class AmountFunction extends SourceFunction<Amount<? extends Quantity>,Amount<? extends Quantity>>{

    private static String name = "Function of amounts";
    private static String description = "A mathematical function of source data";

    
    /**Creates a new Amount function with an initial amount source
     * 
     * @param initialValue initial value of the source function
     */
    public AmountFunction(DataSource<Amount<? extends Quantity>> initialValue) {
        super(name, description, (DataSource<Amount<?>>) initialValue);
    }
    
    /**
     * Creates a new Amount function with an initial value of the dimensionless
     * value = 1.
     */
    public AmountFunction() {
        super(name, description, Amount.ONE);
    }
    
    /**Multiply this function with an amount data source
     * 
     * @param argument the multiplier
     * @return this function
     */

    public AmountFunction multiply(DataSource<Amount<? extends Quantity>> argument) {
        recordOperation(new MultipyOperator(argument));
        return this;
    }
    
    /**Divide this function with an amount data source
     * 
     * @param argument the divider
     * @return this function
     */
    public AmountFunction divide(DataSource<Amount <? extends Quantity>> argument){
        recordOperation(new DivideOperaor(argument));
        return this;
    }
    
    /**Adds an amount data source to this function (requires 
     * equal dimensions of the function and the source)
     * 
     * @param argument the addition
     * @return this function
     */
    public AmountFunction plus(DataSource<Amount <? extends Quantity>> argument){
        checkCompability(this, argument);
        recordOperation(new PlusOperator(argument));
        return this;
    }
    
    
    
    /**Subtract an amount data source from this function. Requires equal
     * dimensions of function and argument.
     * 
     * @param argument the subtracter
     * @return this function
     */
    public AmountFunction minus(DataSource<Amount <? extends Quantity>> argument){
        checkCompability(argument, this);
        recordOperation(new MinusOperaor(argument));
        return this;
    }
    
    /**Checks compatibility of two DataSources and throws an 
     * IllegalArgumentAcceptions if they are not
     * 
     * @param aThis 
     * @param aThat 
     */
    private void checkCompability(DataSource<Amount<? extends Quantity>> aThis, DataSource<Amount<? extends Quantity>> aThat){
        Unit<?> argUnit=aThat.readValue().getUnit();
        Unit<?> thisUnit=aThis.readValue().getUnit();
        if(!argUnit.isCompatible(thisUnit)){
            throw new IllegalArgumentException("function and adition not of same dimension. "
                    + thisUnit +" not compatible with " + argUnit);
        }
    }

    /**
     * An {@link Operator} for multiplication 
     */
    class MultipyOperator extends Operator<Amount<? extends Quantity>, DataSource<Amount<? extends Quantity>>> {

        public MultipyOperator(DataSource<Amount<? extends Quantity>> argument) {
            super(argument);
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand, Activity dataUser) {
            return operand.times(getArgument().useValue(dataUser));
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand) {
           return operand.times(getArgument().readValue());
        }
        
        

        @Override
        String getOperationSign() {
            return "*";
        }
    }
    
    /**
     * An {@link Operator} for addition
     */

    class PlusOperator extends Operator<Amount<? extends Quantity>, DataSource<Amount <? extends Quantity>>> {

        public PlusOperator(DataSource<Amount<? extends Quantity>> argument) {
            super(argument);
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand, Activity dataUser) {
            return operand.plus(getArgument().useValue(dataUser));
        }

        @Override
        String getOperationSign() {
            return "+";
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand) {
            return operand.plus(getArgument().readValue());
        }
    }
    
    /**
     * An {@link Operator} for division
     */
    class DivideOperaor extends Operator<Amount<? extends Quantity>, DataSource<Amount <? extends Quantity>>> {

        public DivideOperaor(DataSource<Amount<? extends Quantity>> argument) {
            super(argument);
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand, Activity dataUser) {
            return operand.divide(getArgument().useValue(dataUser));
        }

        @Override
        String getOperationSign() {
            return "/";
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand) {
            return operand.divide(getArgument().readValue());
        }
    }
    
    /**
     * An {@link operator} for subtraction
     */
    class MinusOperaor extends Operator<Amount< ? extends Quantity>, DataSource<Amount <? extends Quantity>>> {

        public MinusOperaor(DataSource<Amount<? extends Quantity>> argument) {
            super(argument);
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand, Activity dataUser) {
            return operand.minus(getArgument().useValue(dataUser));
        }

        @Override
        String getOperationSign() {
            return "-";
        }

        @Override
        Amount<? extends Quantity> operate(Amount<? extends Quantity> operand) {
            return operand.minus(getArgument().readValue());
        }
    }
}
